The syntax of Scala is relatively complex. In the spirit of incremental software development, learning a programming language should start with finding an expressive way that one can master and then gradually build upon. Scala supports both object-oriented and functional programming, which is one reason for its complex syntax. Some tutorials are very comprehensive, but this comprehensiveness can make it difficult to distill the main points.
The following content focuses on the simplest basic syntax, hoping that based on this content, one can try to write object-oriented style Scala code.
Scala allows statements to end without a ;
, similar to JavaScript.
val
defines immutable variables (constants), var
defines mutable variables:
val msg1 = "Hello World"
var msg2 = "Hello Wrold"
val msg3: String = "Hello World"
When defining variables, type declarations are on the right side of the variable and are optional; they can be omitted as the compiler will automatically infer the type. Basic types in Scala include:
Byte, Short, Int, Long, Char, String, Float, Double, Boolean
Functions are methods. Below is an example of defining a function:
def max(x: Int, y: Int): Int = {
if (x > y) {
return x
} else {
return y
}
}
The significant differences from Java in method definitions are: first, using the def
keyword to define functions; second, type declarations are on the right side of the variable; third, an =
connects the function declaration and the function body.
Note that function parameters must have explicitly defined types; the compiler cannot automatically infer parameter types. The return type is optional unless the function uses recursion. Additionally, the return
keyword is also optional; if there is no explicit return statement, the program will return the last computed result.
If the statement after if
is a single statement, braces can be omitted. Therefore, the function can also be written like this:
def max2(x: Int, y: Int) = if (x > y) x else y
The above example has already used an if
statement. Scala’s if
statement is not special, but compared to other languages, Scala uses pattern matching to replace the traditional switch
structure:
val a = 1
a match {
case 1 => println(1)
case 2 => println(2)
case _ =>
}
The _
wildcard matches all values and is used to capture the default case. In match expressions, cases never fall through to the next case, so no break
or return
is needed (if _
is placed first, the program will not continue executing). However, be careful, if the program does not match any case, it will throw a MatchError
.
The while
loop is not the recommended style of code in Scala:
var i = 0
while (i < 5) {
println(i)
i += 1
}
This is a typical while
loop. Compared to imperative languages, Scala does not have the ++
operator; one must use statements like i += 1
.
Mentioning while
will inevitably bring up for
. The for
loop in Scala has some differences from imperative languages. In the following example, the program will print from 0 to 4:
for (i <- 0 until 5) {
println(i)
}
Scala does not recommend while
loops and prefers a functional programming style. The foreach
method is one of them:
"abc".foreach(c => println(c))
The program will print the characters a
, b
, c
each on a new line. If the function body has only one statement and one parameter, the code can be more concise:
"abc".foreach(println)
Scala’s arrays are not implemented at the language level and can be used by instantiating the Array
class. Correspondingly, array subscripts are represented using parentheses (i.e., method parameters):
val greet = new Array
greet(0) = "a"
greet(1) = "b"
greet(2) = "c"
greet.foreach(println)
When instantiating objects, default parameters can be directly passed. Array is indeed just a regular class; the following way of writing does not involve any black magic, it just uses a case class, which will be mentioned later.
val greet2 = Array("a", "b", "c")
greet2.foreach(println)
Classes are defined using the class
keyword, and classes contain fields and methods, i.e., typical object-oriented features. Unlike Python, Scala still supports access control:
class Accumulator {
private var sum = 0
def add(b: Byte): Unit = {
sum += b
println(sum)
}
}
Singleton objects are equivalent to static classes in Java and are defined using the object
keyword instead of class
. Singleton objects are shared by the program and can be called directly. Singleton objects can serve as the entry point of the program by defining the main
method within the singleton object. The following program instantiates an object a
from the previously defined Accumulator
class and calls its add
method, ultimately printing 1
:
object Run {
def main(args: Array[String]): Unit = {
val a = new Accumulator
a.add(1)
}
}
In the same source file, when a singleton object and a class have the same name, the singleton object is called the companion object of the class, and the class is the companion class of the singleton object. A class can access the private properties and methods of its companion object.
Scala’s rules for constructors are stricter than Java’s. Scala implements constructors through the concept of class parameters:
class Accumulator(a: Int, b: Int)
If the class has no body, braces can be omitted. When instantiating this class, parameters need to be passed. In Java, overloaded constructors correspond to auxiliary constructors in Scala, which look like this:
class Accumulator(a: Int, b: Int) {
def this(c: Int) = this(c, 1)
}
This class now has two constructors:
val a1 = new Accumulator(1)
val a2 = new Accumulator(1, 2)
The strictness of Scala constructors lies in the fact that the second constructor can only leverage the first constructor or the superclass constructor.
Scala’s inheritance is not significantly different from Java’s, except that method overriding must use the override
keyword:
class A(a: Int) {
def test = println("a")
}
class B(b: Int) extends A(b) {
override def test = println("b")
}
Traits are similar to singleton objects, except for the keyword used during definition. Traits, like regular classes, can contain fields and methods. The significance of traits lies in supporting mixins and allowing multiple traits to be mixed in. This feature is often compared with multiple inheritance.
trait A {
def aMethod = println("A")
}
trait B {
def bMethod = println("B")
}
class C extends A with B
This way, an instance of C
can call aMethod
and bMethod
:
val c = new C
c.aMethod
c.bMethod
Case classes are defined by adding the case
keyword before the class
. This modifier allows the Scala compiler to automatically add some convenient settings to the class: 1. Instances can be created without the new
keyword; 2. Parameters are automatically treated as class fields; 3. The class automatically has toString
, hashCode
, and equals
methods:
case class A(a: Int) {
def aMethod = println(a)
}
object Run {
def main(args: Array[String]): Unit = {
val a = A(1)
a.aMethod // 1
println(a) // A(1)
println(a.a) // 1
}
}
Compared to Java, Scala supports abstract classes but not interfaces. Abstract classes are defined using abstract
, and interfaces are replaced by traits. Scala also supports generics, annotations, and other syntax.
The above content is not comprehensive and may not be sufficient. To use a programming language, in addition to mastering its basic syntax, one should also be familiar with its idiomatic usage, especially for multi-paradigm languages like Scala. This content will be continuously updated and improved, and other features of Scala will be discussed further.